﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading.Tasks;
using System.Data;
using System.Diagnostics;
using System.Drawing.Imaging;

namespace Shrestha.Charts.Graph
{
    public class LocationLineDrawer
    {
        private Bitmap _b;
        private const int MAXNUMBER = 50;

        private const int MAXCOLOR = 200;
        private const int MINCOLOR = 50;

        public String OutputFile { get; set; }

        public void Save() { if(OutputFile != null) _b.Save(OutputFile); }

        private readonly DataView _dv;
        private readonly DrawingManager _dm;
        private readonly Runner _r;

        public Bitmap GetBitmap()
        {
            return _b;
        }

        public int Height { get; set; }
        public int Width { get; set; }

        public LocationLineDrawer(int width, int height)
        {
            this.Height = height;
            this.Width = width;
            this._b = new Bitmap(width, height);
        }

        public LocationLineDrawer(int width, int height, DataView dv, DrawingManager dm, Runner r)
        {
            this.Height = height;
            this.Width = width;
            this._b = new Bitmap(width+1, height+1);
            this._dv = dv;
            this._r = r;
            this._dm = dm;
        }

        private Color GetColor(int maxval, int minval, int currentval, Color cbase, double pow)
        {
            int maxcolor = MAXCOLOR - MINCOLOR;
            double c = (Math.Pow(maxcolor, 1 / pow) * (currentval - minval)) / (maxval - minval);
            c = Math.Pow(c, pow);
            c = (c > maxcolor) ? maxcolor : c;
            return Color.FromArgb((byte)((int)c + MINCOLOR), cbase.R, cbase.G, cbase.B);
        }

        private Color GetColor(int maxval, int minval, int currentval, Color cbase)
        {
            return Color.FromArgb((byte)((currentval - minval) * 255 / (maxval - minval)),
            cbase.R, cbase.G, cbase.B);
        }

        private void HandlePixel(int x, int y, Color c)
        {
            _b.SetPixel(x, y, c);
        }

        public void Draw()
        {
            Stopwatch s = new Stopwatch();
            DataTable dt = _dv.ToTable();

            if (_r.ContainsGradient)
            {

                dt.Columns.Add("t", typeof(int));
                int mingval = int.MaxValue;
                int maxgval = int.MinValue;
                foreach (DataRow dr in dt.Rows)
                {
                    int val = int.Parse(dr["gradient"].ToString());
                    dr["t"] = val;
                    mingval = Math.Min(mingval, val);
                    maxgval = Math.Max(maxgval, val);
                }

                dt.DefaultView.Sort = "t asc";
                dt = dt.DefaultView.ToTable();

                s.Start();
                foreach (DataRow dr in dt.Rows)
                {
                    var a = (int)_dm.GetYFromLatitude((double)dr[_r.LatName]);
                    var b = (int)_dm.GetYFromLongitude((double)dr[_r.LngName]);
                    int c = (int)dr["t"];
                    Line(0, Width, a, b, GetColor(maxgval, mingval, c, Color.DimGray, 0.18));
                }
                s.Stop();
            }
            else
            {
                s.Start();

                var results = (from row in dt.AsEnumerable()
                             group row by new
                                 {
                                     Latitude = row.Field<double>(_r.LatName),
                                     Longitude = row.Field<double>(_r.LngName)
                                 }
                                 into g
                                 select new
                               {
                                   Latitude = g.Key.Latitude,
                                   Longitude = g.Key.Longitude,
                                   t = g.Count()
                               }).Distinct().ToList();
                int mingval = (from r in results select r.t).Min();
                int maxgval = (from r in results select r.t).Max();

                foreach (var r in results)
                {
                    Line(0, Width, (int)_dm.GetYFromLatitude(r.Latitude), (int)_dm.GetYFromLongitude(r.Longitude), GetColor(maxgval, mingval, (int)r.t, Color.Red, 0.2));
                    //Line(0, Width, (int)_dm.GetYFromLatitude(r.Latitude), (int)_dm.GetYFromLongitude(r.Longitude), Color.LightGray);
                }
                

                //foreach (DataRow dr in dt.Rows)
                //{
                //    var a = (int)_dm.GetYFromLatitude((double)dr[_r.LatName]);
                //    var b = (int)_dm.GetYFromLongitude((double)dr[_r.LngName]);
                //    Line(0, Width, a, b, Color.LightGray);
                //}
                s.Stop();
            }
            
            
            //Smooth(_b, 9);
            Console.WriteLine(s.Elapsed);
        }

        public void DrawParallel()
        {
            DataTable dt = _dv.ToTable();
            dt.Columns.Add("t", typeof(int));
            int mingval = int.MaxValue;
            int maxgval = int.MinValue;
            foreach (DataRow dr in dt.Rows)
            {
                int val = int.Parse(dr[_r.GradientName].ToString());
                dr["t"] = val;
                mingval = Math.Min(mingval, val);
                maxgval = Math.Max(maxgval, val);
            }

            Parallel.For(0, dt.Rows.Count, i =>
            {
                var a = (int)_dm.GetYFromLatitude((double)dt.Rows[i][_r.LatName]);
                var b = (int)_dm.GetYFromLongitude((double)dt.Rows[i][_r.LngName]);
                int c = (int)dt.Rows[i]["t"];
                Line(0, Width - 1, a, b, GetColor(maxgval, mingval, c, Color.Red, 1));
            });
        }

        /// <summary>
        /// Bresenham Line drawing algorithm:
        /// function line(x0, y0, x1, y1)
        ///     dx := abs(x1-x0)
        ///     dy := abs(y1-y0) 
        ///     if x0 < x1 then sx := 1 else sx := -1
        ///     if y0 < y1 then sy := 1 else sy := -1
        ///     err := dx-dy
        ///     loop
        ///       plot(x0,y0)
        ///       if x0 = x1 and y0 = y1 exit loop
        ///       e2 := 2*err
        ///       if e2 > -dy then 
        ///         err := err - dy
        ///         x0 := x0 + sx
        ///       end if
        ///       if x0 = x1 and y0 = y1 exit loop
        ///       if e2 <  dx then 
        ///         err := err + dx
        ///         y0 := y0 + sy 
        ///       end if
        ///     end loop
        /// </summary>
        /// <param name="x1"></param>
        /// <param name="x2"></param>
        /// <param name="y1"></param>
        /// <param name="y2"></param>
        /// <param name="color"></param>

        private void Line(int x1, int x2, int y1, int y2, Color color)
        {
            //int dx = Math.Abs(x2 - x1);
            //int dy = Math.Abs(y2 - y1);

            //int sx, sy, e2;
            ////sx = x1 < x2? 1: -1;
            //sx = 1;
            //sy = y1 < y2? 1: -1;

            //int err = dx - dy;

            //while (x1 != x2 && y1 != y2)
            //{
            //    HandlePixel(x1, y1, color);
            //    e2 = 2 * err;
            //    if(e2 > dy){
            //        err = err - dy;
            //        x1 += sx;
            //    }

            //    if(e2 < dx){
            //        err = err +dx;
            //        y1 += sy;
            //    }
            //    Console.WriteLine(x1 + "," + x2 + " and " +y1+","+y2);
            //}

            int slope;
            int dx, dy, incE, incNE, d, x, y;
            // Reverse lines where x1 > x2
            if (x1 > x2)
            {
                Line(x2, y2, x1, y1, color);
                return;
            }
            dx = x2 - x1;
            dy = y2 - y1;
            // Adjust y-increment for negatively sloped lines
            if (dy < 0)
            {
                slope = -1;
                dy = -dy;
            }
            else
            {
                slope = 1;
            }
            // Bresenham constants
            incE = 2 * dy;
            incNE = 2 * dy - 2 * dx;
            d = 2 * dy - dx;
            y = y1;
            // Blit
            for (x = x1; x <= x2; x++)
            {
                HandlePixel(x, y, color);
                if (d <= 0)
                {
                    d += incE;
                }
                else
                {
                    d += incNE;
                    y += slope;
                }
            }
        }
        



        public bool Conv3x3(Bitmap b, ConvMatrix m)
        {
            // Avoid divide by zero errors
            if (0 == m.Factor)
                return false; Bitmap

            // GDI+ still lies to us - the return format is BGR, NOT RGB. 
            bSrc = (Bitmap)b.Clone();
            BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
                                ImageLockMode.ReadWrite,
                                PixelFormat.Format24bppRgb);
            BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height),
                               ImageLockMode.ReadWrite,
                               PixelFormat.Format24bppRgb);
            int stride = bmData.Stride;
            int stride2 = stride * 2;

            System.IntPtr Scan0 = bmData.Scan0;
            System.IntPtr SrcScan0 = bmSrc.Scan0;

            unsafe
            {
                byte* p = (byte*)(void*)Scan0;
                byte* pSrc = (byte*)(void*)SrcScan0;
                int nOffset = stride - b.Width * 3;
                int nWidth = b.Width - 2;
                int nHeight = b.Height - 2;

                int nPixel;

                for (int y = 0; y < nHeight; ++y)
                {
                    for (int x = 0; x < nWidth; ++x)
                    {
                        nPixel = ((((pSrc[2] * m.TopLeft) +
                            (pSrc[5] * m.TopMid) +
                            (pSrc[8] * m.TopRight) +
                            (pSrc[2 + stride] * m.MidLeft) +
                            (pSrc[5 + stride] * m.Pixel) +
                            (pSrc[8 + stride] * m.MidRight) +
                            (pSrc[2 + stride2] * m.BottomLeft) +
                            (pSrc[5 + stride2] * m.BottomMid) +
                            (pSrc[8 + stride2] * m.BottomRight))
                            / m.Factor) + m.Offset);

                        if (nPixel < 0) nPixel = 0;
                        if (nPixel > 255) nPixel = 255;
                        p[5 + stride] = (byte)nPixel;

                        nPixel = ((((pSrc[1] * m.TopLeft) +
                            (pSrc[4] * m.TopMid) +
                            (pSrc[7] * m.TopRight) +
                            (pSrc[1 + stride] * m.MidLeft) +
                            (pSrc[4 + stride] * m.Pixel) +
                            (pSrc[7 + stride] * m.MidRight) +
                            (pSrc[1 + stride2] * m.BottomLeft) +
                            (pSrc[4 + stride2] * m.BottomMid) +
                            (pSrc[7 + stride2] * m.BottomRight))
                            / m.Factor) + m.Offset);

                        if (nPixel < 0) nPixel = 0;
                        if (nPixel > 255) nPixel = 255;
                        p[4 + stride] = (byte)nPixel;

                        nPixel = ((((pSrc[0] * m.TopLeft) +
                                       (pSrc[3] * m.TopMid) +
                                       (pSrc[6] * m.TopRight) +
                                       (pSrc[0 + stride] * m.MidLeft) +
                                       (pSrc[3 + stride] * m.Pixel) +
                                       (pSrc[6 + stride] * m.MidRight) +
                                       (pSrc[0 + stride2] * m.BottomLeft) +
                                       (pSrc[3 + stride2] * m.BottomMid) +
                                       (pSrc[6 + stride2] * m.BottomRight))
                            / m.Factor) + m.Offset);

                        if (nPixel < 0) nPixel = 0;
                        if (nPixel > 255) nPixel = 255;
                        p[3 + stride] = (byte)nPixel;

                        p += 3;
                        pSrc += 3;
                    }

                    p += nOffset;
                    pSrc += nOffset;
                }
            }

            b.UnlockBits(bmData);
            bSrc.UnlockBits(bmSrc);
            return true;
        }

        public bool Smooth(Bitmap b, int nWeight /* default to 1 */)
        {
            ConvMatrix m = new ConvMatrix();
            m.SetAll(1);
            m.Pixel = nWeight;
            m.Factor = nWeight + 8;

            return Conv3x3(b, m);
        }


    }
}
